Default draft protocol support: sessionless + handshake-less (SEP-2575 + SEP-2567)#1610
Default draft protocol support: sessionless + handshake-less (SEP-2575 + SEP-2567)#1610halter73 wants to merge 16 commits into
Conversation
bb9f572 to
30782f6
Compare
…d (SEP-2575, SEP-2567) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…options (MCP9005) Default `HttpServerTransportOptions.Stateless` to true so new code on the 2026-07-28 draft revision (SEP-2567) is sessionless from the start. Mark the surface that only makes sense in the legacy stateful HTTP mode as obsolete behind the new MCP9005 diagnostic so callers see a deprecation hint but can still pin Stateless = false to keep using session-based behaviors during back-compat: * `HttpServerTransportOptions.EventStreamStore` (resumability) * `HttpServerTransportOptions.SessionMigrationHandler` (multi-node migration) * `HttpServerTransportOptions.PerSessionExecutionContext` * `HttpServerTransportOptions.IdleTimeout` * `HttpServerTransportOptions.MaxIdleSessionCount` Internal infrastructure that legitimately reads those options for the back-compat stateful path now suppresses MCP9005 at the use site. Test projects suppress it globally via NoWarn because the suite intentionally exercises both modes. Update tests/samples that previously relied on the implicit `Stateless = false` default to set it explicitly: * TestSseServer.Program — SSE always needs stateful state shared across GET/POST. * ConformanceServer.Program — resumability + OAuth conformance scenarios are stateful. * ResumabilityIntegrationTestsBase — resumability is a stateful concern. * SseIntegrationTests / MapMcpSseTests — SSE requires stateful. * OAuthTestBase — OAuth flow uses the GET /sse session-based endpoint. * MrtrProtocolTests / SessionMigrationTests / StreamableHttpServerConformanceTests — these tests intentionally drive the legacy stateful session machinery. * DraftHttpHandlerTests — tests draft rejection of GET/DELETE endpoints, which are only mapped when Stateless = false. Rework HTTP header conformance helpers (HttpHeaderConformanceTests + StreamableHttpServerConformanceTests) to stop asserting an mcp-session-id response header from draft/non-draft initialize, because the sessionless default means none is returned. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…to legacy protocol Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…of overwriting Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…Detect fallback Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
… #2855) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…pec-version strings Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- RawStreamConformanceTests.cs: wrap in #if !NET472 to avoid ReadLineAsync(CancellationToken) overload missing on .NET Framework. - HttpMcpServerBuilderExtensionsTests: IdleTrackingBackgroundService_StartsTimer_WhenStateful needs explicit Stateless=false after the default flipped to true in commit 8904958. - HttpHeaderConformanceTests: two tests used the old DRAFT-2026-v1 wire-version string which the server now rejects; updated to 2026-07-28. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
d9277e0 to
f3f6843
Compare
The server validated that Mcp-Param-* header values are conformantly encoded (printable ASCII, or a "=?base64?...?=" wrapper for non-ASCII) but applied no such validation to the standard Mcp-Name header. Raw non-ASCII Mcp-Name values were passed through and compared byte-for-byte against the body name. Mirror the existing Mcp-Param-* validation for Mcp-Name: reject values containing characters outside the valid HTTP header value range, then decode the "=?base64?...?=" wrapper before comparing to the body value. This makes the server reject mis-encoded non-ASCII names and correctly accept compliant base64-wrapped non-ASCII tool/resource/prompt names. Fixes HttpHeaderConformanceTests.Server_RejectsInvalidUtf8EncodedHeaderValue, which previously passed only incidentally on the stateful draft path. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
The draft 2026-07-28 protocol removes stream resumability entirely (a dropped connection is treated as cancellation), so the SSE event stream store surface is legacy-only. Mark the resumability interfaces, options, and wire-up [Obsolete] under the existing MCP9005 (LegacyStatefulHttp) diagnostic, matching the already-obsoleted stateful HTTP options: - ISseEventStreamReader / ISseEventStreamWriter / ISseEventStreamStore - SseEventStreamMode / SseEventStreamOptions - StreamableHttpServerTransport.EventStreamStore - DistributedCacheEventStreamStoreOptions - WithDistributedCacheEventStreamStore Internal SDK usage of these now-obsolete types is suppressed with targeted MCP9005 pragmas (and project-level NoWarn where source generators emit code over the obsolete types). External consumers still receive the obsolete warning. Behavior is unchanged. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Under the draft revision (SEP-2575 + SEP-2567) the HTTP request lifetime is the request lifetime: there are no sessions, so a dropped connection is equivalent to cancelling the in-flight request. Verify that aborting the HTTP request flows cancellation into a running tool handler's CancellationToken, covering both draft sessionless mode and legacy stateless mode (both are 1:1 request-to-handler). The tests drive raw HTTP via the in-memory Kestrel transport: a tool blocks on its injected CancellationToken, the client aborts the request mid-flight, and the tests assert the server observes RequestAborted and the tool's token fires. No production change was required; the existing session-disposal path already propagates the abort. These pin that behavior going forward. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…scenarios Adds the two SEP-2322 ConformanceServer tools that RunMrtrConformanceTest previously skipped as "not yet implemented": - test_input_required_result_tampered_state: R1 issues an HMAC-signed requestState; R2 with a tampered requestState surfaces a -32602 JSON-RPC error (McpProtocolException propagates as a protocol error, not an isError CallToolResult). - test_input_required_result_capabilities: emits inputRequests only for the capabilities the client declared on the per-request _meta clientCapabilities envelope (read via JsonRpcMessageContext.ClientCapabilities). Removes the per-row Skip from both [InlineData] rows so they run under the same HasMrtrScenarios() gate as the other MRTR scenarios. Verified live: 14/14 RunMrtrConformanceTest scenarios pass against the local compat/conformance-draft build (which emits the 2026-07-28 wire string). Adds in-process wire-level regression coverage in MrtrProtocolTests (TamperedRequestState_ReturnsJsonRpcError and CapabilityCheck_OnlyEmitsInputRequestsForDeclaredCapabilities) so both behaviors stay verified in CI even while the published conformance package's draft wire string lags this SDK. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…atch When a draft request's MCP-Protocol-Version header disagrees with the per-request _meta io.modelcontextprotocol/protocolVersion value (SEP-2575), the server already rejected the request in PopulateContextFromMeta, but it used -32602 InvalidParams. A conformant draft client's server/discover probe treats any non-modern JSON-RPC error (including InvalidParams) as a legacy-server signal and falls back to the initialize handshake. That means a modern draft server that detected a genuine header/body mismatch would be misread as legacy. Emit -32001 HeaderMismatch instead -- the same code already used for the Mcp-Method/Mcp-Name header-vs-body checks and the exact code the client's probe recognizes as a modern-server signal to surface as-is (see McpClientImpl's catch (McpProtocolException ex) when (ex.ErrorCode == McpErrorCode.HeaderMismatch)). Adds a RawHttpConformanceTests regression asserting a header/_meta protocol-version mismatch yields -32001. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Flip McpClientImpl.ConnectAsync so a null ProtocolVersion (the default) prefers the draft revision (SEP-2575 + SEP-2567): the client probes with server/discover and transparently falls back to the legacy initialize handshake when the server doesn't support draft. The legacy branch now runs only when the caller explicitly pins a non-draft version, making draft opt-out rather than opt-in. Merge (rather than overwrite) the session-level client capabilities into each request's _meta envelope so per-request opt-ins already written by higher layers (e.g. the tasks-extension capability from GetMetaWithTaskCapability) survive now that draft _meta injection is the default path. Refresh the XML docs on McpClientOptions.ProtocolVersion / MinProtocolVersion, McpSession.DraftProtocolVersion, and McpSessionHandler.DraftProtocolVersion to describe the new default. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
docs/concepts/stateless/stateless.md references xref:list-of-diagnostics#obsolete-apis, but list-of-diagnostics.md had no uid front matter, so `make generate-docs` (docfx --warningsAsErrors) failed on every CI job that reached it. Add the uid following the existing stateless.md pattern; the ## Obsolete APIs heading already resolves to the obsolete-apis anchor. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
With the client default flipped to draft, every test that builds a default client now negotiates server/discover instead of initialize. Sweep the suite: tests whose purpose is the legacy initialize handshake, Mcp-Session-Id lifecycle, session resumption/reconnect, DELETE, event-stream polling, or server->client sampling over a persistent stream are pinned to 2025-11-25 (these behaviors don't exist under the sessionless draft revision); handshake-agnostic tests run on the draft default with incidental assertions (message counts, captured initialize requests, session-id headers) adjusted. The ConformanceClient pins the legacy "initialize" and "sse-retry" scenarios while letting the others exercise the draft probe plus transparent legacy fallback. Draft sampling/elicitation coverage is retained via the stdio MRTR tests (ClientServerTestBase / MapMcpTests.Mrtr). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
mikekistler
left a comment
There was a problem hiding this comment.
Looks good! 👍
Copilot found a few things worth mentioning. I'll leave it to you to decide if any of these require changes.
| // legacy server that silently drops unknown methods (per stdio.mdx fallback rules). | ||
| // The probe timeout is bounded by InitializationTimeout, but we cap it at 5s so we | ||
| // can quickly fall back when a server isn't going to respond. | ||
| var probeTimeout = TimeSpan.FromSeconds(5); |
There was a problem hiding this comment.
The 5-second probeTimeout for server/discover is hardcoded with no option to configure it. For high-latency environments (e.g., cold-start serverless, satellite links), 5s may be too aggressive and could falsely trigger the legacy fallback. Consider exposing this as a property on McpClientOptions or at least making it a named constant with a doc comment explaining the rationale.
| /// <summary>Tracks an active <c>subscriptions/listen</c> subscription for notification fan-out.</summary> | ||
| private sealed record ActiveSubscription(RequestId Id, SubscriptionsListenNotifications Granted, LoggingLevel? LogLevel); | ||
|
|
||
| private readonly ConcurrentDictionary<RequestId, ActiveSubscription> _activeSubscriptions = new(); |
There was a problem hiding this comment.
The _activeSubscriptions dictionary is populated here and removed on cancellation, but I don't see code that actually routes notifications to matching subscriptions (e.g., sending tools/list_changed only to subscriptions that have toolsListChanged = true). Is the fan-out implemented elsewhere, deferred to a follow-up PR, or am I missing where it happens?
| if (!string.Equals(_negotiatedProtocolVersion, protocolVersion, StringComparison.Ordinal)) | ||
| { | ||
| _negotiatedProtocolVersion = protocolVersion; | ||
| _sessionHandler.NegotiatedProtocolVersion = protocolVersion; |
There was a problem hiding this comment.
Under draft protocol, concurrent requests can each carry different per-request _meta/protocolVersion values. The writes to _negotiatedProtocolVersion and _sessionHandler.NegotiatedProtocolVersion here are not atomic with each other and not synchronized against concurrent reads from notification/telemetry code. Is this intentional (last-writer-wins is acceptable for telemetry), or should this be guarded?
| if (context.ClientCapabilities is { } clientCapabilities && IsStatefulSession()) | ||
| { | ||
| // Defensive merge instead of overwrite. SEP-2575 says the per-request envelope is | ||
| // the client's full capabilities, but PR #1579's GetMetaWithTaskCapability emits a | ||
| // partial envelope (only extensions.io.modelcontextprotocol/tasks) on every | ||
| // tools/call regardless of the negotiated protocol version. If we overwrote here, | ||
| // a legacy client that called initialize with { Elicitation = new() } would lose | ||
| // its elicitation capability the moment it issued a tools/call. Merging non-null | ||
| // fields preserves whatever the initialize handshake (or a prior, more complete | ||
| // envelope) established. | ||
| // | ||
| // The IsStatefulSession() gate prevents leaking per-request capability state into | ||
| // _clientCapabilities under StreamableHttpServerTransport { Stateless = true } | ||
| // (where _clientCapabilities is otherwise null and StatelessServerTests rely on | ||
| // that invariant to surface the "X is not supported in stateless mode" errors). | ||
| _clientCapabilities = MergeClientCapabilities(_clientCapabilities, clientCapabilities); |
There was a problem hiding this comment.
The comment explains why merging is necessary (partial envelopes from the Tasks extension), but SEP-2575 says "Servers MUST NOT infer client capabilities from previous requests." This merge carries forward capabilities from initialize into subsequent draft requests. If a client intentionally drops a capability it previously declared, the server would still "remember" it via the merge. The IsStatefulSession() guard helps (stateless servers won't merge), but for stateful sessions a client can't un-declare a capability mid-session. Is this a known spec deviation that should be documented?
| [JsonConverter(typeof(TimeSpanMillisecondsConverter))] | ||
| public TimeSpan? TimeToLive { get; set; } | ||
|
|
||
| /// <inheritdoc /> | ||
| /// <remarks> | ||
| /// Spec PR #2855 makes <c>cacheScope</c> a required field on <see cref="DiscoverResult"/>. The | ||
| /// server emits a safe default (<see cref="Protocol.CacheScope.Private"/>) on draft sessions | ||
| /// when the application has not set an explicit value. | ||
| /// </remarks> | ||
| [JsonPropertyName("cacheScope")] | ||
| [JsonConverter(typeof(CacheScopeConverter))] | ||
| public CacheScope? CacheScope { get; set; } | ||
| } |
There was a problem hiding this comment.
The remarks say spec PR #2855 makes ttlMs and cacheScope required fields on DiscoverResult, but they're declared as nullable (TimeSpan? / CacheScope?). The ConfigureDiscover handler always populates them with safe defaults, but since these are public set, a consumer building their own DiscoverResult could leave them null and produce a non-conformant response. Should these be required properties (like SupportedVersions, Capabilities, ServerInfo) to enforce the spec constraint at compile time?
Summary
Implements the draft MCP protocol revision (
2026-07-28) in the C# SDK — removing theinitializehandshake andMcp-Session-Idper SEP-2575 and SEP-2567, while preserving back-compat with legacy clients/servers via probe-and-fallback negotiation.Stacked on the now-merged #1458 (MRTR). Opt in to draft by setting
ProtocolVersion = McpSessionHandler.DraftProtocolVersion.What's in
Protocol
DraftProtocolVersionvalue set to"2026-07-28"(spec string, replaces MRTR's"DRAFT-2026-v1"placeholder).server/discoverregistered on every server; serves as the bootstrap mechanism (clients send it first under draft)._meta.io.modelcontextprotocol/protocolVersionis validated server-side; unsupported versions return-32004UnsupportedProtocolVersionErrorwith{supported, requested}data.ttlMs+cacheScopeadded toDiscoverResultper spec PR #2855; defaults tottlMs: 0+cacheScope: "private"under draft (immediate-stale, not shareable) for safe back-compat behavior.Transport
HttpServerTransportOptions.Statelessdefaults totruefor new code.EventStreamStore,SessionMigrationHandler,PerSessionExecutionContext,IdleTimeout,MaxIdleSessionCount, plusISseEventStreamStore/ISessionMigrationHandler) are marked[Obsolete(MCP9005)]— seedocs/list-of-diagnostics.md.Client negotiation
400 Bad Requestit parses the body — modern JSON-RPC errors (-32004,-32003,-32001) surface asMcpProtocolExceptionto the caller; any other JSON-RPC error (legacy-32600,-32601,-32700, parse fail, empty body) → switch to legacy andinitialize. Matches spec PR #2844 ("the fallback MUST NOT be keyed to a single error code").server/discoverfirst.DiscoverResult→ modern.-32004with shaped data → retry withsupported[]. Anything else, or silence past the 5-second probe timeout → fall back toinitializeon the same stdin/stdout (no process restart per spec).AutoDetectingClientSessionTransportnow recognizes JSON-RPC error envelopes in HTTP 400 bodies; adopts StreamableHttp instead of silently falling back to SSE on modern-error responses.Public API
McpClientOptions.MinProtocolVersion : string?— when set, the client refuses to fall back below this version and surfaces a clearMcpExceptioninstead. Useful for strict-modern production code and for tests that want to assert draft-only behavior.What's tested
tests/ModelContextProtocol.Tests/Server/RawStreamConformanceTests.cs— drivesMcpServerdirectly via pairedPipestreams withoutMcpClient. 5 tests coveringserver/discoverfirst, drafttools/callafter no init,-32004on unsupported version, legacyinitializestill works, dual-era dispatch on the same stream.tests/ModelContextProtocol.AspNetCore.Tests/RawHttpConformanceTests.cs— drives the C# server with rawHttpClientagainst in-memory Kestrel. 5 tests covering drafttools/callwith full_meta, rawserver/discover,-32004on unsupportedMCP-Protocol-Versionheader, legacyinitializeon the default (stateless+draft) server, andGET /mcpreturning405when not stateful.tests/ModelContextProtocol.AspNetCore.Tests/DraftHttpFallbackTests.cs): three Kestrel-in-memory cases covering Python-shape (-32600 legacy error), Go-shape (-32004 withsupporteddata), and HeaderMismatch-shape (-32001) on real HTTP. Plus null-id parser tests and HeaderMismatch passthrough test on the in-memory transport.HttpTaskIntegrationTests) now explicitly opt intoStateless = false.d9277e0c:ModelContextProtocol.Tests, net9.0, excluding env-dependentClientIntegrationTests/DockerEverythingServerTestsand the env-quirk-onlyStdioClientTransportTests.EscapesCliArgumentsCorrectlywhich depends on local PATH/CMD.EXE config): 2052 passed / 4 skipped. The full suite reports 72 fails forEscapesCliArgumentsCorrectly, all on a parameterized test that'sgit diff origin/main..HEAD = 0(i.e. unchanged in this PR); CI on main is green.ModelContextProtocol.AspNetCore.Tests, net9.0): 482 passed / 3 failed / 29 skipped. The 3 failures are all pre-existing on main:Server_RejectsInvalidUtf8EncodedHeaderValue,RunConformanceTest_Sep2243("http-custom-headers")(the SEP-2243 finding below), andRunCachingConformanceTest(parallel-run port collision; passes in 1s in isolation).Cross-SDK compatibility (Phase 7 + Phase 11d)
Validated against the other Tier-1 SDKs (TypeScript, Python, Go) in their current
main/ draft-branch states. Wire-trace artifacts kept in this branch's session state.sep-2575-2567-draft-protocol)tools/listsucceedssimple-streamablehttp-stateless(origin/main)-32600, falls back to legacyinitialize, negotiates2025-06-18simple-tool(origin/main)server/discover, gets-32601, falls back toinitializeon the same stdin/stdout, negotiates2025-06-18-32004in 400 body, adopts StreamableHttp, retries legacyinitializewith2025-11-25, lists 10 toolsserver/discovernatively; C# negotiates down to2025-11-25compat/go-draft-forkwith version-string + exported opt-in patches)server/discoverandtools/listsimple-toolclientinitializewith max2025-06-18; C# server (stateless default) serves single-shot legacy sessionα-findings fixed in this PR (post-cross-SDK testing)
ccdd4223simple-streamablehttp-statelessreturnsid: nullon errors before the request id can be determined).00d57f71McpProtocolExceptionper spec PR #2844 (not just modern -32004/-32003) so the connect-time fallback chain can dispatch on the error code.276bde45initialize.3778e00eAutoDetectingClientSessionTransportnow recognizes JSON-RPC error envelopes in HTTP 400 bodies; adopts StreamableHttp instead of silently falling back to SSE.β-findings (peer-SDK issues, informational)
compat/ts-draft) doesn't yet emitMcp-Method/Mcp-Nameheaders (the fix is on a different branch). Closure awaits upstream merge.mainno longer crashes on draft probe, now returns clean JSON-RPC error envelope.origin/mainstill uses2026-06-30version string and unexportedClientSessionOptions.protocolVersion. Documented; patches applied locally for cross-SDK testing only.Conformance suite (Phase 12)
Ran the upstream
@modelcontextprotocol/conformancesuite against the C# SDK. Two tracks:Track A — bump the published npm pin
Bumped
tests/Common/Utils/package.jsonfrom0.1.16→0.2.0-alpha.2(d539e7fd). This activates 5 previously-gated test classes (ClientConformanceTests.RunConformanceTest_Sep2243,ServerConformanceTests.RunConformanceTest_HttpHeaderValidation,ServerConformanceTests.RunConformanceTest_HttpCustomHeaderServerValidation,ServerConformanceTests.RunMrtrConformanceTest,CachingConformanceTests.RunCachingConformanceTest).Because
0.2.0-alpha.2still emits the placeholder wire versionDRAFT-2026-v1(the spec-aligned2026-07-28only landed in unpublishedalpha.3), a wire-version-match gate (HasMatchingDraftWireVersion()intests/Common/Utils/NodeHelpers.cs, commitf3698c71) is ANDed into each draft-onlyHasXxxskip predicate so the 14 draft-scenario rows skip cleanly under the published alpha.2 instead of failing with mismatched-string assertions.Track B — local build of
compat/conformance-draftAssembled a local
compat/conformance-draftbranch inmodelcontextprotocol/conformance(tip50ad0fa) by merging the following SEP-relevant open PRs on top ofmain:#310 (SEP-2549 absence-assert) was skipped — too-deep conflict with main's
RunContextrefactor (PRs #319 / #317 / #321 / #318). Deferred to a follow-up.Installed locally with⚠️ Note:
npm install --no-save H:\modelcontextprotocol\conformance.npm cireverts to pinnedalpha.2; reviewers reproducing locally must re-run the path-install after dependency restore. Flipped 3--spec-version DRAFT-2026-v1references inServerConformanceTests.cs+ 1 inCachingConformanceTests.csto2026-07-28(commitd9277e0c), and renamed 6 tools + 1 prompt inIncompleteResultTools.cs/IncompleteResultPrompts.csto match conformance's rename ofincomplete-result-* → input-required-result-*(mirrors the SDK's MRTRIncompleteResult → InputRequiredResultrename).Outcome (serial run on stateless HTTP):
input-required-result-*scenarios — thetampered-state(HMAC-protected requestState) andcapability-check(per-request capability-aware inputRequest gating) rows are now implemented inConformanceServerand un-skipped — plus bothSep2243.http-{standard,invalid-tool}-headersand CachingClientConformanceTests.RunConformanceTest_Sep2243("http-custom-headers")— not a C# bug: the harness scenario putsx-mcp-headeron atype: "number"parameter, which SEP-2243 forbids (the spec's earlier self-contradiction was resolved againstnumberupstream in modelcontextprotocol/modelcontextprotocol#2863). The C# client correctly excludes the malformed tool, so noMcp-Param-*headers are sent. Stays skipped until a conformant package ships; tracked in #1655.Modes: only stateless HTTP exercised so far. Stateful HTTP and stdio modes deferred to a follow-up — Track B already validates draft conformance on the most important transport, and the published-pin gate (Track A) ensures CI on pinned alpha.2 keeps working without local conformance-build dependencies.
Parallel-run flakiness:
CachingConformanceTestshows a port-pool collision (port 301x range) under parallel xUnit collections; passes consistently in isolation in under 2 s. Documented as known-flaky-in-parallel; the test suite was not switched to serial.Out of scope
InitializeRequestParams,Mcp-Session-Idconstants,PingRequestParams, …) are still current in2025-11-25and remain un-obsoleted in this PR.2024-11-05) transport stays mapped under/sseand/messagefor legacy back-compat.Resolved during review (originally punted, now done in this PR)
-32001) validation — the server already compared the HTTPMCP-Protocol-Versionheader against the body_meta.io.modelcontextprotocol/protocolVersion, but threw-32602 InvalidParams. A draft client'sserver/discoverprobe treats any non-modern error (includingInvalidParams) as a legacy signal and falls back toinitialize, so a modern server detecting a genuine mismatch was misread as legacy. It now emits-32001 HeaderMismatch— the code the client recognizes as a modern-server signal — with aRawHttpConformanceTestsregression.input-required-result-tampered-stateHMAC +input-required-result-capability-checkper-request gating) are now implemented inConformanceServerand un-skipped (14/14RunMrtrConformanceTestpass against the localcompat/conformance-draftbuild), with in-process wire-level regressions inMrtrProtocolTests.Punted to follow-up PRs
mcp-session-idheader: server returns400rather than silently ignoring it. This is the deliberate choice — rejecting surfaces the client bug, and SEP-2567 removes the header from the draft revision so "ignore" is a robustness option, not a requirement. Not tracking (closed Draft server: validate body/header protocolVersion mismatch and no-op stray Mcp-Session-Id #1654).IsStatefulSession()gate review inMcpServerImpl.IsMrtrSupported(the existing TODO from the MRTR PR) — tracked in Draft follow-up tidy-ups: configurable stdio probe timeout + IsStatefulSession/IsMrtrSupported gate review #1652.Mcp-Param-*header emission forx-mcp-headerontype: "number"parameters: no change — SEP-2243 forbidsnumber(resolved upstream in (chore): sep-to-spec consistency pass modelcontextprotocol#2863), so the harnesshttp-custom-headersscenario is the non-conformant party. Tracked in Upstream conformance harness: http-custom-headers tests float x-mcp-header (forbidden by SEP-2243) #1655 (re-enable the xunit case once upstream ships a conformant package).#310) after the conformanceRunContextrefactor settles, (3) file upstream issue for missingserver/discoverstandalone scenario — tracked in Conformance Track B: stateful HTTP/stdio modes, SEP-2549 absence-assert, remaining MRTR scenarios #1653.